---
description: >
  Basic secure web development best practices in Ruby.
title: Secure Web Development
---

_Check out [secure response headers](/cheat-sheets/secure-response-headers){:rel="noopener"}
and [secure response body](/cheat-sheets/secure-response-body){:rel="noopener"} for more notes._

<details markdown="1" id="table-of-contents">
<summary>
Table of Contents
</summary>

* TOC
{:toc}
</details>

## Secure Coding

> From the fragments of source code mentioned in the Advisory, I felt
> that with such coding style there should still be security issues remained in
> FTA if I kept looking.
> &mdash; <cite>[Orange Tsai](https://devco.re/blog/2016/04/21/how-I-hacked-facebook-and-found-someones-backdoor-script-eng-ver/){:rel="nofollow noreferrer noopener"}</cite>


- Verify for security early.
- Keep a consistent code style.
- Review code frequently. Security is never done.
- Security concerns over development convenience.
  + Review dependencies' security practices.
  + Use redundant protection mechanism.
  + Forbid everything by default. ie. whitelists, least privilege principle.
  + Validate all input.
  + Sanitize valid input.
  + Interpolate sanitized input.
  + Encrypt sensitive data. eg. User identifiable data, business classified data.
  + Encode all output according to its type and delivery mechanism.
  + Consider possible exceptions, errors, and bugs.
  + Ensure exceptions don't reveal too much as to create security holes.
  + Rescue specific exceptions rather than generic ones.
  + Don't rely on implementation secrecy to keep security.
- Test Security.
  + Add settings tests to prevent:
    * Configuration tampering.
    * Unexpected changes to dependency defaults.
  + Mimic attacker actions.
- Keep tools up-to-date for bug and security patches.
- Never commit private credentials to a repo.
- Change private credentials when team members leave.
- Use environments to maintain a single codebase.

### Environments

- Never store production data in other environments.
  + Use fake data.
  + Scrub data from tests reproducing bugs.
- Store production credentials only in [env vars](https://www.digitalocean.com/community/tutorials/how-to-read-and-set-environmental-and-shell-variables-on-a-linux-vps){:rel="nofollow noopener noreferrer"}.
- Manage environment credentials independently. Avoid grouping by environment.
- Never reuse credentials from any environment.
- Limit production environment access to white-listed IPs/users.
- Check no genuine transactions can be executed in staging/demo environments.

Assuming Bundler for gem management.

#### Gemfile

- Set runtime dependencies in the main body.
- Group & install other gems by environment.
- Configure the patch-level upgrade for each gem.

```ruby
# Gemfile

gem "roda", "~> 3.0"

group :development do
  gem "minitest", "~> 5.10"
end
```

### Maintenance

Keep a maintenance schedule to:

- Audit the codebase.
- Audit dependencies.
- Update gems.
  + update gems individually.
  + run tests before and after an update.
  + commit each gem update individually.
- Audit Logs.
  + review own log files.
  + review data captured by error monitoring services.
  + purge old logs.
- DB backups.
- Renew private keys before expiration.

## Input Handling

Proper input handling prevents [code injection](https://en.wikipedia.org/wiki/Code_injection){:rel="nofollow noopener noreferrer"}.
In general:

- Ensure client validations are also done on the server.
- Validate input as soon as it's received.
- Validate semantics.
  + Presence.
  + Length.
  + Type.
  + Format.
- Sanitize according to syntactical validations.
  + Enforce structure.
  + Whitelist safe elements.
  + Encode special elements.
- Never escape special characters. It always depends on the output type.

### Regex

When validating format:

- Use `\A` and `\z` anchors to help prevent code injection.

```ruby
greetings = "hello world\n<script>malicious_js();</script>"

/\Ahello world\z/.match? greetings # => false
/^hello world$/.match? greetings # => true
```

### Unicode

Given unicode's range of characters:

- Normalize input.
  + Ensure canonical encoding. ie. `"\u00e9"` and `"\u0065\u0301"` should be equivalent.
  + Handle invalid characters.
- Whitelist by character category. eg. CJK ideographs, Tibetan, etc.
- Whitelist individual characters.

### XSS

Cross-Site Scripting attacks embed and trigger malicious scripts on
either server or client.
A sound XSS defence covers both input and output.

Check out [secure response headers](/cheat-sheets/secure-response-headers){:rel="noopener"}
and [secure response body](/cheat-sheets/secure-response-body){:rel="noopener"}
for their respective XSS prevention practices.

- Encode all data controlled by end-users.
- Sanitize valid input according to its type. eg. HTML, CSS.
  + Whitelist elements.
  + Encode valid elements.
- Encode using security-focused gems to avoid pitfalls.

### URLs

- Validate URL schemes. eg. HTTPS, FTPS.
- Escape special characters. eg. `URI.escape`.
- Whitelist URLs meant to be used as `src`.
- Sanitize `params` values.
- Test for malicious `params` values.
- Interpolate URL fragments rather than passing it in full.

### Database

- Avoid saving null.
- Add DB constraints to prevent data corruption.
- Escape special characters before storing input.
- Prevent mass assignment. Whitelist mutable attributes.

#### SQLi

To prevent SQL injection:

- Interpolate input when building SQL queries.
- Type cast attribute values to match DB data types.
- Test common SQLi attacks to ensure basic coverage.

Test ORM for SQLi to prevent unexpected changes.

### Files

- Ensure uploads to the app server are not allowed.
- Use security-focused gems and/or services that:
  + Validate files whitelisting:
    * Name's character set.
    * Names to prevent overwrites.
    + File extensions.
  + Validate MIME types.
  + Scan files for viruses/malware.
  + Upload directly to a storing server.
  + Process media files asynchronously.

### CMDi

Avoid passing input as system call arguments to prevent command injection.

```ruby
# Kernel
exec
system
`echo 'backticks'`
%x{}
syscall
command
open
```

Whenever it can't be avoided:

- Use security-oriented command wrapping gems.
- Add tests to ensure secure versions of system libraries.
- Keep an `allowed_commands` list.

Pass commands as string fragments rather than a single string. Optionally,
pass env vars as a hash.

```ruby
def run flag:
  system { "ENV_VAR" => "value" },
    "command", "argument", "--option #{allowed_flags[flag]}"
end
```

### Paths

- Ensure this kind of input can't be avoided.
- Turn paths into their canonical version to check their absolute path.
- Limit access to paths within the app.
  + Whitelist safe paths.
  + Interpolate path segments.

In ruby, consider using classes such as `Pathname`, or methods such as
`Dir.chroot` before allowing users to pass paths as arguments.


### Ruby dangerous methods

In Ruby, we can help prevent remote code execution when we avoid passing
user controlled/generated input to:

Subprocesses

```ruby
# Kernel
fork
spawn
```

Dynamic code loading methods

```ruby
# Kernel
load
autoload
require
require_relative
```

Metaprogramming methods

```ruby
# Object
send
__send__
public_send

# BasicObject
instance_eval
instance_exec

# Module
eval
class_eval
class_exec
module_eval
module_exec
alias_method
binding.eval
```

Furthermore, avoid passing input to any method in `Fiddle` module.


#### Deserialization

Stick to `YAML`, and `JSON` when serializing data. To avoid arbitrary
code execution, make sure to deserialize it using default settings.

```ruby
require "yaml"
require "json"

YAML.safe_load
JSON.parse
```

## HTTPS
The HTTPS protocol is used to securely exchange data with clients. The server
must obtain a valid TLS (aka SSL) certificate to ensure others it can
securely exchange data. We can get free certificates from [Let's Encrypt](https://letsencrypt.org/docs/){:rel="nofollow noopener noreferrer"}.
However, how to setup HTTPS is beyond the scope of this cheat sheet.

## Authentication & Authorization
- Enforce authentication using well tested gems.
- Test authentication for every restricted action.
- Grant each role enough privileges to get their job done.
- Test authorization scope. eg. Users can't change each other's details.
- Never send passwords through notification channels. eg. email.

### Authentication Details

Rely on well designed gems that cover as many of these features as possible:

- Encourage high [entropy](https://crypto.stackexchange.com/a/376){:rel="nofollow noopener noreferrer"} passwords.
  + Don't limit character set.
  + Configurable max length. Opt-in 128 characters length.
  + Prevent empty passwords. Opt-in min. 10 characters.
  + Prevent password reuse.
  + Prevent using [common passwords](https://github.com/danielmiessler/SecLists/tree/master/Passwords){:rel="nofollow noopener noreferrer"}.
  + Prevent using [pwoned passwords](https://haveibeenpwned.com/Passwords){:rel="nofollow noopener noreferrer"}.
- Password hashing using either:
  + Argon2i
  + Scrypt
  + Bcrypt
- Manage session tokens to prevent session fixation.
- Password reset:
  + Display generic message eg. we've sent more details to the registered email.
  + Never lock users out immediately. Send a reset-or-ignore message.
  + Send reset password message even when user doesn't exist but w/link to create account.
- Limit amount of password resets per hr/days.
- Verify password before editing sensitive data.
- Prevent user enumeration and guessable accounts.
  + Respond failed logins with status code `400`.
  + Use generic error message. ie. Authentication failed.
  + If 2FA, pass 1st w/invalid token. Fail 2nd.
  + cron emails eg. password reset, 2FA, so on.
- Prevent timing attacks.
  + Increase report time with each failed login attempt.
  + Limit the number of login attempts per second.
- Role based access control (RBAC).
- Opt-in store password hashes in a restricted table.
- Opt-in multi-factor authentication (FA).
- Opt-in timeout sessions.
- Opt-in limit simultaneous sessions per user.

### Registration

- Require unique identifier, eg. username.
- Verify account.
- Opt-in multiple contact channels. ie. email, signal, wire.
- Opt-in multi-factor authentication.
- Opt-in notifications when user logs in from new IP.

#### User Privacy

- Avoid using identifiable user data, such as email, as user identifiers.
- Use indirect object references. eg. unique displayable id.
- Request email only for verification.
- Ask the least amount of identifiable data.
- Encrypt identifiable data before storing it.

## Router

- Prevent open redirect attacks. Redirect only to:
  + Internal paths, and services.
  + Trusted 3rd party services.
- Sanitize `params`.
  + Whitelist keys.
  + Validate & sanitize values.
- Turn off detailed error reports in production.
  + Return `400` with a generic error page.
  + Remove sensitive data before logging errors.
- Throttle requests to prevent DDOS attacks.
- Help prevent CSRF validating requests headers:
  + `Origin`.
  + `Referrer`.

### Open redirection

If for legitimate reasons, we allow input-generated redirection:

- Whitelist sites to redirect to.
- Confirm forwarding. Clearly state destination.
- Add tests to:
  + Prevent whitelist tampering.
  + Refute `location` header can be an invalid url.

## Cache

- Prevent web cache deception attacks.
  + Only cache assets with a verifiable caching HTTP header.
    * verify assets via [`integrity` attribute](https://search-and-deploy.gitlab.io/cheat-sheets/secure-response-body/#subresource-integrity){:rel="nofollow noreferrer noopener"}.
  + Cache resource by contents, not extension.
- Store all static resources in a designated directory.
- Ensure when `/public/non-existent.css` is requested by authenticated user:
  + Never respond with `202`.
  + Never render a page, eg. home, as fallback.
  + Return `404` response.
  + Redirection via `302` is ok.

## Robots

Robots usually categorize (index) websites and/or extract (crawl) their data.

Beware, robot rules are merely advisory. A robot can simply ignore them.

- Include index/crawling rules in `robots.txt`.
- Include `robots.txt` in the root route.
- Add authentication to prevent index/crawling:
  + Private, and sensitive data.
  + Hosted non-production environments.
  + Restricted areas.
- For granular rules use:
  + the `X-Robots-Tag` response header.
  + meta-tags.

A `robots.txt` which doesn't allow any robot to index/crawl a site looks like:

```
User-agent: *
Disallow: /
```

## Logs

- Filter before logging:
  + Identifiable data.
  + Private data.
  + Restricted actions.
- Ensure logging services only get filtered data.

## Gems & Tools

Code quality:
- [flog](https://github.com/seattlerb/flog){:rel="nofollow noopener noreferrer"}. Code complexity analyzer.
- [flay](https://github.com/seattlerb/flay){:rel="nofollow noopener noreferrer"}. Code structure analyzer.
- [debride](https://github.com/seattlerb/debride/){:rel="nofollow noopener noreferrer"}. Dead code detector.
- [reek](https://github.com/troessner/reek){:rel="nofollow noopener noreferrer"}. Code-smell detector.
- [rubocop](https://github.com/bbatsov/rubocop){:rel="nofollow noopener noreferrer"}. Ruby style analyzer.
- [bundler-audit](https://github.com/rubysec/bundler-audit){:rel="nofollow noopener noreferrer"}. Gem security audit helper.

Security test:
- [OWASP Zed Attack Proxy](https://github.com/zaproxy/zaproxy){:rel="nofollow noopener noreferrer"}. Automated security scanner.
- [Gauntlt](https://github.com/gauntlt/gauntlt){:rel="nofollow noopener noreferrer"}. Cucumber-based security testing framework.
- [Nikto](https://github.com/sullo/nikto){:rel="nofollow noopener noreferrer"}. Web server scanner.

Encryption:
- [RbNaCl](https://github.com/cryptosphere/rbnacl){:rel="nofollow noopener noreferrer"}.
- [Underlock](https://github.com/metaware/underlock){:rel="nofollow noopener noreferrer"}.
- [Sym](https://github.com/kigster/sym){:rel="nofollow noopener noreferrer"}.
- [sekrets](https://github.com/ahoward/sekrets){:rel="nofollow noopener noreferrer"}.

Credential Management:
- [git secrets](https://github.com/awslabs/git-secrets){:rel="nofollow noopener noreferrer"}. Prevent committing AWS
private credentials.

Input handling:
- [Sanitize](https://github.com/rgrove/sanitize/){:rel="nofollow noopener noreferrer"}. Whitelist-based HTML and CSS
sanitizer.
- [Loofah](https://github.com/flavorjones/loofah){:rel="nofollow noopener noreferrer"}. HTML/XML manipulation and
sanitzation gem.
- [Pathname](https://ruby-doc.org/stdlib-2.4.1/libdoc/pathname/rdoc/Pathname.html){:rel="nofollow noopener noreferrer"}.
Ruby standard library for handling paths.

HTTPS scanners:
- [Pshtt](https://github.com/dhs-ncats/pshtt){:rel="nofollow noopener noreferrer"}. HTTPS best practices scanner.
- [SSL Server Test](https://www.ssllabs.com/ssltest/){:rel="nofollow noopener noreferrer"}. Free TLS configuration analysis.

Rack solutions to generate/renew Let's Encrypt certificates:
- [Acme-Client](https://github.com/unixcharles/acme-client){:rel="nofollow noopener noreferrer"}.
- [LetsEncrypt Heroku](https://github.com/xijo/letsencrypt_heroku){:rel="nofollow noopener noreferrer"}.

Rack compatible authentication & authorization gems:
- [Rodauth](https://github.com/jeremyevans/rodauth){:rel="nofollow noopener noreferrer"}.
- [Warden](https://github.com/hassox/warden){:rel="nofollow noopener noreferrer"}.

Logging:
- [semantic logger](https://github.com/rocketjob/semantic_logger){:rel="nofollow noopener noreferrer"}


## Recommendations

Although outside the scope of this cheat sheet, consider:

- Disable compressed responses to prevent compression based attacks.
- Require latest browser versions.
- Require browser never accepts 3rd party cookies to prevent Heist attack.

## Resources

### Guides & Cheat Sheets

- [Input Validation Cheat Sheet](https://www.owasp.org/index.php/Input_Validation_Cheat_Sheet){:rel="nofollow noopener noreferrer"}.
- [Cross-Site Scripting Prevention Cheat Sheet](https://www.owasp.org/index.php/XSS_(Cross_Site_Scripting)_Prevention_Cheat_Sheet){:rel="nofollow noopener noreferrer"}.
- [Password Storage Cheat Sheet](https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet){:rel="nofollow noopener noreferrer"}.
- [OWASP Top Ten Cheat Sheet](https://www.owasp.org/index.php/OWASP_Top_Ten_Cheat_Sheet){:rel="nofollow noopener noreferrer"}.
- [Code Review Guide](https://www.owasp.org/index.php/Category:OWASP_Code_Review_Project){:rel="nofollow noopener noreferrer"}.
- [OWASP Testing Project](https://www.owasp.org/index.php/OWASP_Testing_Project){:rel="nofollow noopener noreferrer"}.
- [Proactive Controls](https://www.owasp.org/index.php/OWASP_Proactive_Controls){:rel="nofollow noopener noreferrer"}.
- [Session Management Cheat Sheet](https://www.owasp.org/index.php/Session_Management_Cheat_Sheet){:rel="nofollow noopener noreferrer"}.
- [Authentication Cheat Sheet](https://www.owasp.org/index.php/Authentication_Cheat_Sheet){:rel="nofollow noopener noreferrer"}.

### Articles

- [Secure Input and Output Handling](https://en.wikipedia.org/wiki/Secure_input_and_output_handling){:rel="nofollow noopener noreferrer"}.
- [Gemfile Docs](https://bundler.io/v1.15/man/gemfile.5.html){:rel="nofollow noopener noreferrer"}.
- [Ruby Style Guide](https://github.com/bbatsov/ruby-style-guide){:rel="nofollow noopener noreferrer"}.
- [The SaaS CTO Security Checklist](https://cto-security-checklist.sqreen.io/){:rel="nofollow noopener noreferrer"}.
- [Ruby CVEs in the NVD](https://web.nvd.nist.gov/view/vuln/search-results?query=Ruby&search_type=all&cves=on){:rel="nofollow noopener noreferrer"}.
- [Command Injection](https://www.owasp.org/index.php/Command_Injection){:rel="nofollow noopener noreferrer"}.
- [HTTP Response Splitting](https://www.owasp.org/index.php/HTTP_Response_Splitting){:rel="nofollow noopener noreferrer"}.
- [How to Migrate to HTTPS](https://docs.google.com/document/d/1oRXJUIttqQxuxmjj2tgYjj096IKw4Zcw6eAoIKWZ2oQ/preview#){:rel="nofollow noopener noreferrer"}.
- [Sakurity Research](https://sakurity.com/research){:rel="nofollow noopener noreferrer"}.
- [Test for User Enumeration](https://www.owasp.org/index.php/Testing_for_User_Enumeration_and_Guessable_User_Account_(OWASP-AT-002)){:rel="nofollow noopener noreferrer"}.
- [Nvisium Blog](https://nvisium.com/blog/){:rel="nofollow noopener noreferrer"}.
- [Secure Password Storage](https://docs.google.com/document/d/1R6c9NW6wtoEoT3CS4UVmthw1a6Ex6TGSBaEqDay5U7g/edit){:rel="nofollow noopener noreferrer"}.
- [The Twelve-Factor App Configuration](https://12factor.net/config){:rel="nofollow noopener noreferrer"}.
- [User Enumeration](https://securityriskadvisors.com/blog/post/user-enumeration/){:rel="nofollow noopener noreferrer"}.
- [NIST Digital Identity Guidelines](https://www.nist.gov/itl/tig/special-publication-800-63-3).
- [Passwords Evolved](https://www.troyhunt.com/passwords-evolved-authentication-guidance-for-the-modern-era/){:rel="nofollow noopener noreferrer"}.
- [Wordlists for Random Passphrases](https://www.eff.org/deeplinks/2016/07/new-wordlists-random-passphrases){:rel="nofollow noopener noreferrer"}.
- [Resilient Software Engineering](https://speakerdeck.com/ngalbreath/resilient-software-engineering){:rel="nofollow noopener noreferrer"}.
- [US-CERT Coding Practices](https://www.us-cert.gov/bsi/articles/knowledge/coding-practices){:rel="nofollow noopener noreferrer"}.
- [Role-based Access Control](https://en.wikipedia.org/wiki/Role-based_access_control){:rel="nofollow noopener noreferrer"}.
- [Insecure Direct Object References](https://www.owasp.org/index.php/Top_10_2010-A4-Insecure_Direct_Object_References){:rel="nofollow noopener noreferrer"}.
- [The Principle of Least Privilege](https://www.us-cert.gov/bsi/articles/knowledge/principles/least-privilege){:rel="nofollow noopener noreferrer"}.
- [About /robots.txt](https://www.robotstxt.org/robotstxt.html){:rel="nofollow noopener noreferrer"}.
- [Robots meta tag](https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag){:rel="nofollow noopener noreferrer"}.
- [The ultimate guide to the meta robots tag](https://yoast.com/robots-meta-tags/){:rel="nofollow noopener noreferrer"}.
- [HEIST: HTTP Encrypted Information Stolen through TCP-windows](https://tom.vg/papers/heist_blackhat2016.pdf){:rel="nofollow noopener noreferrer"}.
- [Rack::Attack: Rate limits against DDoS and abusive users](https://rorsecurity.info/portfolio/rackattack-rate-limits-against-ddos-and-abusive-users){:rel="nofollow noopener noreferrer"}.
- [Security by Design Principles](https://www.owasp.org/index.php/Security_by_Design_Principles).
- [Web Cache Deception Attack](https://www.blackhat.com/us-17/briefings.html#web-cache-deception-attack){:rel="nofollow noreferrer noopener"}

### Presentations

- [Ruby Web Application Security Defense in Depth](https://code.jeremyevans.net/presentations/rubyhack2018/index.html#1){:rel="nofollow noreferrer noopener"}
- [Addressing Security Regression by Unit Testing](https://www.infoq.com/presentations/security-regression-unit-testing){:rel="nofollow noopener noreferrer"}.
